package com.example.app.util;

import org.springframework.util.StringUtils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class RSA {
	private static final String KEY_ALGORIGHM = "RSA";
	private static final String CIPHER_ALGORIGHM = "RSA/ECB/PKCS1Padding";
	public static final String PUBLIC_KEY = "publicKey";
	public static final String PRIVATE_KEY = "privateKey";
	private static Random random = new Random();
	public static Map<String, String> getKeyEncoded(){
		return getKeyEncoded(1024);
	}
	public static Map<String, String> getKeyEncoded(int keysize){
		Map<String, Object> keyMap = getKey(keysize);
		if (keyMap != null) {
			Map<String, String> keys = new HashMap<String, String>();
			keys.put(PUBLIC_KEY, (new BASE64Encoder()).encodeBuffer(((RSAPublicKey)keyMap.get(PUBLIC_KEY)).getEncoded()));
			keys.put(PRIVATE_KEY, (new BASE64Encoder()).encodeBuffer(((RSAPrivateKey)keyMap.get(PRIVATE_KEY)).getEncoded()));
			return keys;
		}
		return null;
	}
	public static Map<String, Map<String, String>> getKeyModulusAndExponent(){
		Map<String, Map<String, String>> result = new HashMap<String, Map<String,String>>();
		Map<String, Object> keyMap = getKey();
		if (keyMap != null) {
			Map<String, String> privKey = new HashMap<String, String>();
			RSAPrivateKey privateKey = (RSAPrivateKey)keyMap.get(PRIVATE_KEY);
			privKey.put("modulus", privateKey.getModulus().toString(16));
			privKey.put("exponent", privateKey.getPrivateExponent().toString(16));
			result.put(PRIVATE_KEY, privKey);
			Map<String, String> pubKey = new HashMap<String, String>();
			RSAPublicKey publicKey = (RSAPublicKey)keyMap.get(PUBLIC_KEY);
			pubKey.put("modulus", publicKey.getModulus().toString(16));
			pubKey.put("exponent", publicKey.getPublicExponent().toString(16));
			result.put(PUBLIC_KEY, pubKey);
		}
		return result;
	}
	public static Map<String, Object> getKey(){
		return getKey(1024);
	}
	public static Map<String, Object> getKey(int keysize){
		try {
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORIGHM);
			keyPairGenerator.initialize(keysize);
			KeyPair keyPair = keyPairGenerator.generateKeyPair();
			Map<String, Object> keyMap = new HashMap<String, Object>();
			keyMap.put(PUBLIC_KEY, keyPair.getPublic());
			keyMap.put(PRIVATE_KEY, keyPair.getPrivate());
			return keyMap;
		} catch (Exception e) {
			throw new RuntimeException("get key failed", e);
		}
	}
	public static String getPrivateKeyModulus(RSAPrivateKey privateKey) throws Exception{
		return privateKey.getModulus().toString(16);
	}
	public static String getPrivateKeyExponent(RSAPrivateKey privateKey) throws Exception{
		return privateKey.getPrivateExponent().toString(16);
	}
	public static String getPublicKeyModulus(RSAPublicKey publicKey) throws Exception{
		return publicKey.getModulus().toString(16);
	}
	public static String getPublicKeyExponent(RSAPublicKey publicKey) throws Exception{
		return publicKey.getPublicExponent().toString(16);
	}
	public static RSAPrivateKey getPrivateKey(String privateKey){
		try {
			PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec((new BASE64Decoder()).decodeBuffer(privateKey));
			KeyFactory factory = KeyFactory.getInstance(KEY_ALGORIGHM);
			return (RSAPrivateKey) factory.generatePrivate(pkcs8EncodedKeySpec);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	public static RSAPrivateKey getPrivateKey(String modulus, String privateExponent){
		return getPrivateKey(new BigInteger(modulus, 16), new BigInteger(privateExponent, 16));
	}
	public static RSAPrivateKey getPrivateKey(BigInteger modulus, BigInteger privateExponent) {
		try {
			KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORIGHM);
			RSAPrivateKeySpec privateKeySpec = new RSAPrivateKeySpec(modulus, privateExponent);
			return (RSAPrivateKey) keyFactory.generatePrivate(privateKeySpec);
		} catch (Exception e) {
			throw new RuntimeException("cannot build private key by modulues and exponent", e);
		}
	}
	public static RSAPublicKey getPublicKey(String publicKey){
		try {
			X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec((new BASE64Decoder()).decodeBuffer(publicKey));
			KeyFactory factory = KeyFactory.getInstance(KEY_ALGORIGHM);
			return (RSAPublicKey) factory.generatePublic(x509EncodedKeySpec);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	public static RSAPublicKey getPublicKey(String modulus, String publicExponent){
		return getPublicKey(new BigInteger(modulus, 16), new BigInteger(publicExponent, 16));
	}
	public static RSAPublicKey getPublicKey(BigInteger modulus, BigInteger publicExponent) {
		try {
			KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORIGHM);
			RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, publicExponent);
			return (RSAPublicKey) keyFactory.generatePublic(publicKeySpec);
		} catch (Exception e) {
			throw new RuntimeException("cannot build public key by modulues and exponent", e);
		}
	}
	public static String publicKeyEncrypt(String publicKey, String input){
		try {
		    return encrypt(getPublicKey(publicKey), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot encrypt by public key", e);
		}
	}
	public static String publicKeyEncrypt(String modulus, String exponent, String input){
		try {
			return encrypt(getPublicKey(modulus, exponent), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot encrypt by modulus and exponent", e);
		}
	}
	public static String encrypt(RSAPublicKey publicKey, String input){
		try {
			Cipher cipher = Cipher.getInstance(CIPHER_ALGORIGHM);
		    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
		    byte[] b = cipher.doFinal(input.getBytes());
		    return bytesToHex(b);
		} catch (Exception e) {
			throw new RuntimeException("cannot encrypt by public key", e);
		}
	}
	public static String privateKeyEncrypt(String privateKey, String input){
		try {
			return encrypt(getPrivateKey(privateKey), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot encrypt by private key", e);
		}
	}
	public static String privateKeyEncrypt(String modulus, String exponent, String input){
		try {
			return encrypt(getPrivateKey(modulus, exponent), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot encrypt by modulus and exponent", e);
		}
	}
	public static String encrypt(RSAPrivateKey privateKey, String input){
		try {
			Cipher cipher = Cipher.getInstance(CIPHER_ALGORIGHM);
		    cipher.init(Cipher.ENCRYPT_MODE, privateKey);
		    byte[] b = cipher.doFinal(input.getBytes());
		    return bytesToHex(b);
		} catch (Exception e) {
			throw new RuntimeException("cannot encrypt by private key", e);
		}
	}
	public static String publicKeyEncrypt64(String publicKey, String input){
		try {
		    return encrypt64(getPublicKey(publicKey), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot encrypt by public key", e);
		}
	}
	public static String publicKeyEncrypt64(String modulus, String exponent, String input){
		try {
			return encrypt64(getPublicKey(modulus, exponent), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot encrypt by modulus and exponent", e);
		}
	}
	public static String encrypt64(RSAPublicKey publicKey, String input){
		try {
			Cipher cipher = Cipher.getInstance(CIPHER_ALGORIGHM);
		    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
		    return (new BASE64Encoder()).encode(cipher.doFinal(input.getBytes()));
		} catch (Exception e) {
			throw new RuntimeException("cannot encrypt by public key", e);
		}
	}
	public static String privateKeyEncrypt64(String privateKey, String input){
		try {
			return encrypt64(getPrivateKey(privateKey), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot encrypt by private key", e);
		}
	}
	public static String privateKeyEncrypt64(String modulus, String exponent, String input){
		try {
			return encrypt64(getPrivateKey(modulus, exponent), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot encrypt by modulus and exponent", e);
		}
	}
	public static String encrypt64(RSAPrivateKey privateKey, String input){
		try {
			Cipher cipher = Cipher.getInstance(CIPHER_ALGORIGHM);
		    cipher.init(Cipher.ENCRYPT_MODE, privateKey);
		    return (new BASE64Encoder()).encode(cipher.doFinal(input.getBytes()));
		} catch (Exception e) {
			throw new RuntimeException("cannot encrypt by private key", e);
		}
	}
	public static String privateKeyDecrypt(String privateKey, String input){
		try {
			return decrypt(getPrivateKey(privateKey), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot decrypt by private key", e);
		}
	}
	public static String privateKeyDecrypt(String modulus, String exponent, String input){
		try {
			return decrypt(getPrivateKey(modulus, exponent), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot decrypt by modulus and exponent", e);
		}
	}
	public static String decrypt(RSAPrivateKey privateKey, String input){
		try {
			Cipher cipher = Cipher.getInstance(CIPHER_ALGORIGHM);
			cipher.init(Cipher.DECRYPT_MODE, privateKey);
			return new String(cipher.doFinal(hexToBytes(input)));
		} catch (Exception e) {
			throw new RuntimeException("cannot decrypt by private key", e);
		}
	}
	public static String publicKeyDecrypt(String publicKey, String input){
		try {
			return decrypt(getPublicKey(publicKey), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot decrypt by public key", e);
		}
	}
	public static String publicKeyDecrypt(String modulus, String exponent, String input){
		try {
			return decrypt(getPublicKey(modulus, exponent), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot decrypt by modulus and exponent", e);
		}
	}
	public static String decrypt(RSAPublicKey publicKey, String input){
		try {
			Cipher cipher = Cipher.getInstance(CIPHER_ALGORIGHM);
			cipher.init(Cipher.DECRYPT_MODE, publicKey);
			return new String(cipher.doFinal(hexToBytes(input)));
		} catch (Exception e) {
			throw new RuntimeException("cannot decrypt by public key", e);
		}
	}
	public static String privateKeyDecrypt64(String privateKey, String input){
		try {
			return decrypt64(getPrivateKey(privateKey), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot decrypt by private key", e);
		}
	}
	public static String privateKeyDecrypt64(String modulus, String exponent, String input){
		try {
			return decrypt64(getPrivateKey(modulus, exponent), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot decrypt by modulus and exponent", e);
		}
	}
	public static String decrypt64(RSAPrivateKey privateKey, String input){
		try {
			Cipher cipher = Cipher.getInstance(CIPHER_ALGORIGHM);
			cipher.init(Cipher.DECRYPT_MODE, privateKey);
			return new String(cipher.doFinal((new BASE64Decoder()).decodeBuffer(input)));
		} catch (Exception e) {
			throw new RuntimeException("cannot decrypt by private key", e);
		}
	}
	public static String publicKeyDecrypt64(String publicKey, String input){
		try {
			return decrypt64(getPublicKey(publicKey), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot decrypt by public key", e);
		}
	}
	public static String publicKeyDecrypt64(String modulus, String exponent, String input){
		try {
			return decrypt64(getPublicKey(modulus, exponent), input);
		} catch (Exception e) {
			throw new RuntimeException("cannot decrypt by modulus and exponent", e);
		}
	}
	public static String decrypt64(RSAPublicKey publicKey, String input){
		try {
			Cipher cipher = Cipher.getInstance(CIPHER_ALGORIGHM);
			cipher.init(Cipher.DECRYPT_MODE, publicKey);
			return new String(cipher.doFinal((new BASE64Decoder()).decodeBuffer(input)));
		} catch (Exception e) {
			throw new RuntimeException("cannot decrypt by public key", e);
		}
	}
	public static Key createKey(int keysize, String crypt) {
		KeyGenerator keyGenerator;
		try {
			keyGenerator = KeyGenerator.getInstance(crypt);
			keyGenerator.init(keysize, createSecureRandom());
			return keyGenerator.generateKey();
		} catch (Exception e) {
			throw new SecurityException(e);
		}
	}
	public static SecureRandom createSecureRandom() {
		SecureRandom secureRandom = new SecureRandom();
		secureRandom.setSeed(System.currentTimeMillis() + random.nextLong());
		return secureRandom;
	}
	private static String bytesToHex(byte[] bytes) {
		if (bytes == null || bytes.length <= 0) {
			return null;
		}
		StringBuffer buffer = new StringBuffer();
		for (int i = 0; i < bytes.length; i++) {
			String str = Integer.toHexString(bytes[i] & 0xFF);
			if (str.length() < 2) {
				buffer.append("0");
			}
			buffer.append(str);
		}
		return buffer.toString();
	}
	private static byte[] hexToBytes(String hex){
		if (StringUtils.isEmpty(hex)) {
			return null;
		}
		hex = hex.toUpperCase();
		int len = hex.length() / 2;
		char[] hexChars = hex.toCharArray();
		byte[] b = new byte[len];
		for (int i = 0; i < b.length; i++) {
			b[i] = (byte)(charToByte(hexChars[i*2]) << 4 | charToByte(hexChars[i*2+1]));
		}
		return b;
	}
	private static byte charToByte(char c) {
		return (byte)"0123456789ABCDEF".indexOf(c);
	}
}
